package com.terrier.ui.controls
{
    import com.terrier.ui.skins.IWxListSkin;
    import com.terrier.ui.skins.IWxSkinBase;
    import com.terrier.ui.skins.WxSkinManager;
    
    import flash.display.DisplayObject;
    import flash.geom.Rectangle;

    /**
     * 列表组件
     * @author Jarvis.weng
     */
    public class WxList extends WxPanel
    {
        /**
         * @private
         */
        protected var _factory:IWxListRendererFactory = new WxListRendererDefaultFactory();

        protected var _itemList:Array = [];

        protected var _dataProvider:Array = [];

        protected var _scrollY:Number = 0;

        protected var _scrollX:Number = 0;
		
		protected var _maxScrollX:Number = 0;
		
		protected var _minScrollX:Number = 0;
		
		protected var _maxScrollY:Number = 0;
		
		protected var _minScrollY:Number = 0;

        protected var _layoutStrategy:IListLayoutStrategy = new VerticalLayoutStrategy();

        protected var _tileLayout:Boolean;

        protected var _leftPad:int;

        protected var _topPad:int;

        protected var _rightPad:int;

        protected var _bottomPad:int;

        /**
         * 构造函数
         */
        public function WxList()
        {
            super();

            _concernChildLayout = true;
            _contentPane.clipping = true;
            _contentPane.width = _width = 500;
            _contentPane.height = _height = 400;

//            for (var i:int = 0; i < 100; ++i)
//            {
//                _dataProvider.push('label: ' + i);
//            }
        }
		
		public function get maxScrollX():Number
		{
			return _maxScrollX;
		}
		
		public function get minScrollX():Number
		{
			return _minScrollX;
		}

		public function get maxScrollY():Number
		{
			return _maxScrollY;
		}

		public function get minScrollY():Number
		{
			return _minScrollY;
		}
		
        public function get bottomPad():int
        {
            return _bottomPad;
        }

        public function set bottomPad(value:int):void
        {
            _bottomPad = value;
            invalidateLayout();
        }

        public function get rightPad():int
        {
            return _rightPad;
        }

        public function set rightPad(value:int):void
        {
            _rightPad = value;
            invalidateLayout();
        }

        public function get topPad():int
        {
            return _topPad;
        }

        public function set topPad(value:int):void
        {
            _topPad = value;
            invalidateLayout();
        }

        public function get leftPad():int
        {
            return _leftPad;
        }

        public function set leftPad(value:int):void
        {
            _leftPad = value;
            invalidateLayout();
        }

        /**
         * @private
         */
        override public function setSkinID(skinID:String):void
        {
            setSkin(WxSkinManager.getListSkin(skinID));
        }

        /**
         * @private
         */
        override public function setSkin(value:IWxSkinBase):void
        {
            super.setSkin(value);

            invalidateView();
            invalidateLayout();
        }

        /**
         *
         * @return
         */
        public function get factory():IWxListRendererFactory
        {
            return _factory;
        }

        /**
         * @private
         */
        public function set factory(value:IWxListRendererFactory):void
        {
            if (value == null)
            {
                throw new ArgumentError("The factory can not be null.");
                return;
            }

            onFactoryChange();
            _factory.onFactoryChange = null;

            _factory = value;
            _factory.onFactoryChange = onFactoryChange;
        }

        public function get rendererClass():Class
        {
            return _factory.rendererClass;
        }

        public function set rendererClass(value:Class):void
        {
            _factory.rendererClass = value;
        }

        protected function onFactoryChange():void
        {
            clearListItem();

            invalidateLayout();
        }

        public function get dataProvider():Array
        {
            return _dataProvider;
        }

        public function set dataProvider(value:Array):void
        {
            if (_dataProvider != value)
            {
                clearListItem();
            }

            _dataProvider = value;

            invalidateLayout();
        }

        public function get startIndex():int
        {
            if (_itemList.length == 0)
            {
                return 0;
            }
            return (_itemList[0] as ListItemInternal).index;
        }

        public function get endIndex():int
        {
            if (_itemList.length == 0)
            {
                return 0;
            }
            return (_itemList[_itemList.length - 1] as ListItemInternal).index;
        }

        public function get tileLayout():Boolean
        {
            return _tileLayout;
        }

        public function set tileLayout(value:Boolean):void
        {
            if (_tileLayout == value)
            {
                return;
            }
            _tileLayout = value;
            clearListItem();
            _layoutStrategy = _tileLayout ? new TileLayoutStrategy() : new VerticalLayoutStrategy();
        }

        public function get scrollX():Number
        {
            return _scrollX;
        }

        public function set scrollX(value:Number):void
        {
            _scrollX = value;

            invalidateLayout();
        }

        public function get scrollY():Number
        {
            return _scrollY;
        }

        public function set scrollY(value:Number):void
        {
            _scrollY = value;

            invalidateLayout();
        }

        /**
         * @privates
         */
        override protected function updateLayout():void
        {
            super.updateLayout();

            _contentPane.x = _leftPad;
            _contentPane.y = _topPad;
            _contentPane.width = _width - _leftPad - _rightPad;
            _contentPane.height = _height - _topPad - _bottomPad;

            if (_dataProvider.length == 0)
            {
                clearListItem();
                return;
            }

            var forward:Boolean;
            var index:int = 0;
            var bound:Rectangle = new Rectangle(_scrollX, _scrollY, _contentPane.width, _contentPane.height);

            var updateData:Boolean = true;

            if (_itemList.length == 0)
            {
                _scrollX = 0;
                _scrollY = 0;

                forward = true;
                index = 0;
                createRendererAndLayout(index, bound, forward);
                updateData = false;
            }

            var item:ListItemInternal
            var newItemNum:int;

            forward = false;
            item = _itemList[0];
            index = item.index - 1;
            newItemNum = createRendererAndLayout(index, bound, forward);

            forward = true;
            item = _itemList[_itemList.length - 1];
            index = item.index + 1;
            newItemNum += createRendererAndLayout(index, bound, forward);

            _contentPane.scrollY = _scrollY;
            _contentPane.scrollX = _scrollX;

            if (newItemNum > 0)
            {
                clipRenderer();
            }
			
			var maxBound:Rectangle = _layoutStrategy.calculateAllItemBound(_itemList, _dataProvider.length);
			_minScrollX = maxBound.left;
			_maxScrollX = maxBound.right;
			_minScrollY = maxBound.top;
			_maxScrollY = maxBound.bottom;
        }

        override public function invalidateLayout():void
        {
            super.invalidateLayout();
        }

        private function clipRenderer():void
        {
            var bound:Rectangle = new Rectangle(_contentPane.scrollX, _contentPane.scrollY,
                                                _contentPane.width, _contentPane.height);
            for (var i:int = 0; i < _itemList.length; ++i)
            {
                var item:ListItemInternal = _itemList[i];
                if (_layoutStrategy.clipRenderer(bound, item.renderer))
                {
                    disposeListItem(item);
                    _itemList.splice(i, 1);
                    --i;
                }
            }
        }

        private function createRendererAndLayout(index:int, bound:Rectangle, forward:Boolean):int
        {
            var itemNum:int = 0;

            _layoutStrategy.initRendererPostion(_itemList, bound, forward);

            var item:ListItemInternal;

            while (_layoutStrategy.needRenderer(bound, forward))
            {
                if (index < 0 || index >= _dataProvider.length)
                {
                    break;
                }
                item = createListItem(index);
                _contentPane.addChild(DisplayObject(item.renderer));
                _layoutStrategy.layoutRenderer(item, _itemList, bound, forward);

                if (forward)
                {
                    index += 1;
                    _itemList.push(item);
                }
                else
                {
                    index -= 1;
                    _itemList.unshift(item);
                }

                ++itemNum;
            }

            _layoutStrategy.layoutRendererDone(_itemList, forward);

            return itemNum;
        }

        private function createListItem(index:int):ListItemInternal
        {
            var item:ListItemInternal = new ListItemInternal();
            item.index = index;
            item.data = _dataProvider[index];
            item.renderer = _factory.createRenderer(index, item.data);
            return item;
        }

        private function disposeListItem(item:ListItemInternal):void
        {
            _contentPane.removeChildSafely(DisplayObject(item.renderer));
            _factory.disposeRenderer(item.renderer);
        }

        private function clearListItem():void
        {
            while (_itemList.length > 0)
            {
                var item:ListItemInternal = _itemList.pop();
                disposeListItem(item);
            }
        }

        private function get skin():IWxListSkin
        {
            return _skin as IWxListSkin;
        }
    }
}
import com.terrier.ui.core.IWxDataRenderer;
import com.terrier.ui.core.WxHorizontalAlign;
import com.terrier.utils.WxArrayIterator;

import flash.geom.Point;
import flash.geom.Rectangle;

class ListItemInternal
{
    public var index:int; // 当前数据索引

    public var data:Object; // 当前数据

    public var renderer:IWxDataRenderer; // 数据渲染器    

    public var row:int; // 当前的行

    public var column:int; // 当前的列
}

interface IListLayoutStrategy
{
    function initRendererPostion(itemList:Array, bound:Rectangle, forward:Boolean):Point;

    function layoutRenderer(item:ListItemInternal, itemList:Array, bound:Rectangle, forward:Boolean):Point;

    function needRenderer(bound:Rectangle, forward:Boolean):Boolean;

    function layoutRendererDone(itemList:Array, forward:Boolean):void;

    function clipRenderer(bound:Rectangle, renderer:IWxDataRenderer):Boolean;

    function calculateAllItemBound(itemList:Array, totalNum:int):Rectangle;
}

class TileLayoutStrategy implements IListLayoutStrategy
{
    private var maxSize:Number;

    private var currentRow:int;

    private var currentColumn:int;

    private var align:String = WxHorizontalAlign.CENTER;

    private var currentPos:Point;

    public function initRendererPostion(itemList:Array, bound:Rectangle, forward:Boolean):Point
    {
        if (itemList.length == 0)
        {
            currentPos = new Point(bound.left, bound.top);
            currentRow = 0;
            maxSize = 1;
        }
        else
        {
            var item:ListItemInternal = forward ? itemList[itemList.length - 1] : itemList[0];
            currentRow = item.row;
            maxSize = calculatePrevItemOffset(itemList, forward);


            currentPos = forward ? new Point(item.renderer.x + item.renderer.width, item.renderer.y) :
                new Point(item.renderer.x, item.renderer.y + item.renderer.height);
        }
        return currentPos;
    }

    public function layoutRenderer(item:ListItemInternal, itemList:Array, bound:Rectangle, forward:Boolean):Point
    {
        if (forward)
        {
            if (currentPos.x + item.renderer.width > bound.right)
            {
                adjustPrevItem(currentRow, itemList, forward, currentPos, maxSize);
                // 换行
                currentPos.x = 0;
                currentPos.y += maxSize;
                maxSize = 1;
                ++currentRow;
            }

            item.renderer.x = currentPos.x;
            item.renderer.y = currentPos.y;

            item.row = currentRow;

            maxSize = Math.max(item.renderer.height, maxSize);

            currentPos.x += Math.max(item.renderer.width, 1);
        }
        else
        {
            if (currentPos.x - item.renderer.width < bound.left)
            {
                adjustPrevItem(currentRow, itemList, forward, currentPos, maxSize);
                // 换行
                currentPos.x = bound.right;
                currentPos.y -= maxSize;
                maxSize = 1;
                --currentRow;
            }

            item.renderer.x = currentPos.x - item.renderer.width;
            item.renderer.y = currentPos.y - item.renderer.height;

            item.row = currentRow;

            maxSize = Math.max(item.renderer.height, maxSize);

            currentPos.x = item.renderer.x;
        }

        return currentPos;
    }

    public function layoutRendererDone(itemList:Array, forward:Boolean):void
    {
        adjustPrevItem(currentRow, itemList, forward, currentPos, maxSize);
    }

    private function adjustPrevItem(theRow:int, itemList:Array, forward:Boolean, pos:Point, offset:Number):void
    {
        var iterator:WxArrayIterator = new WxArrayIterator(itemList, !forward);
        var posX:Number = 0;
        for (var item:ListItemInternal = iterator.next(); item != null; item = iterator.next())
        {
            if (item.row != theRow)
            {
                break;
            }
            if (forward)
            {
                continue;
            }
            else
            {
                item.renderer.x = posX;
                item.renderer.y = pos.y - offset;
                posX += item.renderer.width;
            }
        }
    }

    private function calculatePrevItemOffset(itemList:Array, forward:Boolean):Number
    {
        var item:ListItemInternal = forward ? itemList[itemList.length - 1] : itemList[0];
        var theRow:int = item.row;
        var offset:Number = 1;

        var iterator:WxArrayIterator = new WxArrayIterator(itemList, !forward);
        for (item = iterator.next(); item != null; item = iterator.next())
        {
            if (item.row != theRow)
            {
                break;
            }
            offset = Math.max(item.renderer.height, offset);
        }

        return offset;
    }

    public function needRenderer(bound:Rectangle, forward:Boolean):Boolean
    {
        if (forward)
        {
            return currentPos.y < bound.bottom;
        }
        else
        {
            return currentPos.y > bound.top;
        }
    }

    public function clipRenderer(bound:Rectangle, renderer:IWxDataRenderer):Boolean
    {
        return renderer.y + renderer.height < bound.top || renderer.y > bound.bottom;
    }

	public function calculateAllItemBound(itemList:Array, totalNum:int):Rectangle
    {
        return new Rectangle();
    }

    public function clear():void
    {

    }
}

class VerticalLayoutStrategy implements IListLayoutStrategy
{
    private var pos:Point;

    public function initRendererPostion(itemList:Array, bound:Rectangle, forward:Boolean):Point
    {
        if (itemList.length == 0)
        {
            pos = new Point(bound.left, bound.top);
        }
        else
        {
            var prevItem:ListItemInternal;

            if (forward)
            {
                prevItem = itemList[itemList.length - 1];
            }
            else
            {
                prevItem = itemList[0];
            }
            pos = forward ? new Point(prevItem.renderer.x, prevItem.renderer.y + prevItem.renderer.height) :
                new Point(prevItem.renderer.x, prevItem.renderer.y);
        }
        return pos;
    }

    public function layoutRenderer(item:ListItemInternal, itemList:Array, bound:Rectangle, forward:Boolean):Point
    {
        var offset:Number = item.renderer.height == 0 ? 10 : item.renderer.height;

        item.renderer.x = pos.x;
        item.renderer.y = forward ? pos.y : pos.y - offset;

        pos.y += forward ? offset : -offset;

        return pos;
    }

    public function needRenderer(bound:Rectangle, forward:Boolean):Boolean
    {
        return (forward && pos.y < bound.bottom) || (!forward && pos.y > bound.top);
    }

    public function clipRenderer(bound:Rectangle, renderer:IWxDataRenderer):Boolean
    {
        return renderer.y + renderer.height < bound.top || renderer.y > bound.bottom;
    }

    public function layoutRendererDone(itemList:Array, forward:Boolean):void
    {
        // nothing to do.
    }

	public function calculateAllItemBound(itemList:Array, totalNum:int):Rectangle
    {
        if (itemList.length == 0)
        {
            return new Rectangle(0, 0, 0, 0);
        }
        var len:int = itemList.length;
        var firstItem:ListItemInternal = itemList[0];
        var lastItem:ListItemInternal = itemList[len - 1];
        var range:int = lastItem.renderer.y + lastItem.renderer.height - firstItem.renderer.y;
        var step:int = range / len;
        var bound:Rectangle = new Rectangle();
		bound.y =  0;
		bound.height = step * totalNum; 
		trace("range:", range, "len:", len, "step:", step);
		return bound;
    }

    public function clear():void
    {

    }
}
